﻿namespace Octopus.OctopusService

open System
open System.IO
open System.Net
open System.Net.Sockets
open System.ServiceProcess
open Octopus.OctopusService.General


type SchedulerService =
  inherit System.ServiceProcess.ServiceBase

  val private listener : TcpListener
  val private components : System.ComponentModel.IContainer

  new() as x =
    {
      listener = new TcpListener(IPAddress.Parse("0.0.0.0"), 17400)
      components = new System.ComponentModel.Container()
    }
    then
      x.ServiceName <- "Octopus Scheduler"

  //--

  member private x.WriteInLog message =
    Database.LogFile.writeInLog message

  //--
  
  member private x.ManageAcceptedClient (client:TcpClient) =
    // Open the socket stream
    // Note: don't declate these with "use" or the objects will be disposed at
    // the end of the block!
    // I need it alive inside the Async block (stream are closed inside there)
    let stream = client.GetStream()
    let sr = new StreamReader(stream)
    let sw = new StreamWriter(stream)
    sw.AutoFlush <- true
    
    let endpoint = client.Client.RemoteEndPoint :?> IPEndPoint
    let clientip = endpoint.Address
      
    let funcToCall =
      // Read the command to execute
      let functionName = sr.ReadLine()
      match functionName with
      | "upgradeNic" -> Functions.upgradeNic
      //| "notifyHpcNodeReady" -> Functions.notifyHpcNodeReady
      | "notifyConfigurationFailed" -> Functions.notifyConfigurationFailed
      | "notifyVmConfigured" -> Functions.notifyVmConfigured clientip
      | "notifyVmIpAddress" -> Functions.notifyVmIpAddress clientip
      | "createNewVm" -> Functions.createNewVm
      | "setVmCheckpoint" -> Functions.setVmCheckpoint
      | "commitVmCheckpoint" -> Functions.commitVmCheckpoint
      | "discardVmCheckpoint" -> Functions.discardVmCheckpoint
      | "stampVm" -> Functions.stampVm
      | "migrateVm" -> Functions.migrateVm
      //| "migrateRunningVm" -> Functions.migrateRunningVm
      | "deleteVm" -> Functions.deleteVm
      | "startVm" -> Functions.startVm
      | "turnoffVm" -> Functions.turnoffVm
      | "suspendVm" -> Functions.suspendVm
      | "getAllVm" -> Functions.getAllVm
      | "getVmByOwner" -> Functions.getVmByOwner
      //| "getTemplatesPath" -> Functions.getTemplatesPath
      | "getVmByGuid" -> Functions.getVmByGuid
      | "getVmStateByGuid" -> Functions.getVmStateByGuid
      //| "getVmStateByGuid_RealTime" -> Functions.getVmStateByGuid_RealTime
      | "getVmIPv4AddressByGuid" -> Functions.getVmIPv4AddressByGuid
      | "getVmRDPAddressByGuid" -> Functions.getVmRDPAddressByGuid
      | "sendMessage" -> Functions.sendMessage
      | "getMessages" -> Functions.getMessages
      | "getAllVmTemplates" -> Functions.getAllVmTemplates
      | "getVmTemplatesById" -> Functions.getVmTemplatesById
      | "createNewVmTemplate" -> Functions.createNewVmTemplate
      
      | "tempCreateDb" -> Functions.tempCreateDb
      | "tempDeleteDb" -> Functions.tempDeleteDb
      | "tempAllVm" -> Functions.tempAllVm
      | "tmpGetHyperVNodes" -> Functions.tmpGetHyperVNodes
       
      | _ -> fun _ sw -> sw.WriteLine(false); sw.WriteLine("Unknown function: " + functionName)
      
    try
      try funcToCall sr sw      
      with e ->
        sw.WriteLine(false);
        sw.WriteLine(e.Message)
        sw.WriteLine(e.StackTrace)
        let msg = "in ManageAcceptedClient: " + e.Message + "\r\n" + e.StackTrace
        x.WriteInLog msg
    finally
      client.Close()
  
  member private x.AcceptTcpClientCallback (ar:IAsyncResult) =
    try
      let client = x.listener.EndAcceptTcpClient(ar)
      Async.Start(
        async {
          try x.ManageAcceptedClient client
          with e -> let msg = "in AcceptTcpClientCallback(0): " + e.Message + "\r\n" + e.StackTrace in x.WriteInLog msg
        }
      )
    with e ->
      let msg = "in AcceptTcpClientCallback(1): " + e.Message + "\r\n" + e.StackTrace
      x.WriteInLog msg

    try
      x.listener.BeginAcceptTcpClient(new AsyncCallback(x.AcceptTcpClientCallback), null) |> ignore
    with e ->
      let msg = "in AcceptTcpClientCallback(2): " + e.Message + "\r\n" + e.StackTrace
      x.WriteInLog msg
    ()

  //--     

  member private x.IsDatabaseConfigured () =
    try
      let x1 = Database.GeneralConfig.get_AdminCredentialsDomain ()
      let x2 = Database.GeneralConfig.get_AdminCredentialsUsername ()
      let x3 = Database.GeneralConfig.get_AdminCredentialsPassword ()
      let x4 = Database.GeneralConfig.get_SharedStoragePath ()
      x1 <> "" && x2 <> "" && x3 <> "" && x4 <> ""
    with _ ->
      false

  member private x.UnzipFile (zipPath:string) =
    use zip = Packaging.ZipPackage.Open(zipPath, FileMode.Open, FileAccess.Read)
    for part in zip.GetParts() do
      let filePath = buildFilePath getServiceBinPath (part.Uri.OriginalString.Replace('/','\\'))
      if filePath.Exists then filePath.Delete()
      use fileStream = new FileStream(filePath.FullName, FileMode.Create)
      use partStream = part.GetStream()
      FunctionsCore.Utility.copyStream partStream fileStream
      partStream.Close()
      fileStream.Close()
    zip.Close()  

  override x.OnStart (args:string array) =
    x.WriteInLog("-- Logging started")
    x.WriteInLog("Trying to start the service...")
    
    try
      try
        //------------------------------------------------------
        let dbpath =
          try getFile getServiceBinPath "Octopus.mdf"
          with _ -> failwithf "Database not found: run the Octopus configurator first."
        //------------------------------------------------------
        let isConfigured = x.IsDatabaseConfigured ()
        if not isConfigured then failwithf "Octopus not configured: run the Octopus configurator first." 
        //------------------------------------------------------
        x.WriteInLog("  Initializing credentials in Hyper-F... ")
        let domain = Database.GeneralConfig.get_AdminCredentialsDomain ()
        let userName = Database.GeneralConfig.get_AdminCredentialsUsername ()
        let password = Database.GeneralConfig.get_AdminCredentialsPassword ()
        HyperF.Init.init_credentials domain userName password
        x.WriteInLog("  done.")
        //------------------------------------------------------
        x.WriteInLog("  Starting the \"HyperV nodes performance\" and \"Virtual Machines status\" monitor... ")
        SchedulerCore.ServiceCoreMainLoop.Init()
        SchedulerCore.ServiceCoreMainLoop.Start()
        x.WriteInLog("  done.")
        //------------------------------------------------------
        x.WriteInLog("  Starting the tcp listener... ")
        x.listener.Start()
        x.listener.BeginAcceptTcpClient(new AsyncCallback(x.AcceptTcpClientCallback), null) |> ignore
        x.WriteInLog("  done.")
        //------------------------------------------------------
        x.WriteInLog("Service started!")
      with e ->
        Database.LogFile.writeBlank()
        x.WriteInLog("Exception: " + e.Message)
        x.WriteInLog(e.StackTrace)
        reraise()
    finally
      x.WriteInLog("-- Log end -----------------------------------------------")
      Database.LogFile.writeBlank()


  override x.OnStop() =
    x.WriteInLog("-- Logging started")
    x.WriteInLog("Stopping the service... ")

    try
      x.WriteInLog("  stopping the listener...")
      x.listener.Stop()
      x.WriteInLog("  done.")
      
      x.WriteInLog(sprintf "  stopping the ServiceCoreMainLoop(%s)..." (SchedulerCore.ServiceCoreMainLoop.ThreadState.ToString()))
      SchedulerCore.ServiceCoreMainLoop.Stop()
      x.WriteInLog("  done.")
    with e ->
      Database.LogFile.writeBlank()
      x.WriteInLog(e.Message)
      x.WriteInLog(e.StackTrace)

    x.WriteInLog("done.")
    x.WriteInLog("-- Log end ------------------------------------------------------")
    Database.LogFile.writeBlank()


  //member x.OnDebugStart (args:string array) = x.OnStart(args)
  //member x.OnDebugStop () = x.OnStop()
    


//
//-------------------------------------------------------------
//

module Main =
  let servicesToRun : ServiceBase array = [| new SchedulerService() |]
  ServiceBase.Run(servicesToRun)



//
// Code recycle bin:
// routine to zip via .net
//
(*
  let zippath = @"C:\Users\mura\Desktop\Ferrari\Octopus\CLIPS libs\x64\Clips64.zip"
  let zip = System.IO.Packaging.ZipPackage.Open(zippath, System.IO.FileMode.Create, System.IO.FileAccess.ReadWrite)
  let files = [| @"C:\Users\mura\Desktop\Ferrari\Octopus\CLIPS libs\x64\CLIPSLib.dll";
                 @"C:\Users\mura\Desktop\Ferrari\Octopus\CLIPS libs\x64\CLIPSNet.dll" |]
  let filesAbs = [| @"C:\Users\mura\Desktop\Ferrari\Octopus\CLIPS libs\x64\CLIPSLib.dll";
                    @"C:\Users\mura\Desktop\Ferrari\Octopus\CLIPS libs\x64\CLIPSNet.dll" |]

  for i in 0 .. (files.Length - 1) do
      let uri = System.IO.Packaging.PackUriHelper.CreatePartUri(new System.Uri(files.[i], System.UriKind.Relative))
      let part = zip.CreatePart(uri, "", System.IO.Packaging.CompressionOption.Maximum)

      let CopyStream (inputStream:System.IO.FileStream) (outputStream:System.IO.Stream) =
          let bufferSize = if (int inputStream.Length) < 1024 then (int inputStream.Length) else 1024
          let buffer = Array.create<byte> bufferSize 0uy
          let mutable bytesRead = 0
          let bytesWritten = 0
          let f = inputStream.Read
          while (bytesRead <- f(buffer, 0, buffer.Length); bytesRead) <> 0 do
            outputStream.Write(buffer, 0, bytesRead)

      let fs = new System.IO.FileStream(filesAbs.[i], System.IO.FileMode.Open, System.IO.FileAccess.Read)
      let dest = part.GetStream()
      CopyStream fs dest
      dest.Close()
      fs.Close()

  zip.Close()
*)
